#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <strings.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <pthread.h>
#include <netdb.h>
#include <fcntl.h>

#include "msg.h"
#include "db.h"
#include "error.h"

// 常量
#define USER_MAX  100
#define FRIEND_MAX  100
#define FILE_SIZE 1024
#define SERV_PORT 8001

// 全局数组
NowClient users[USER_MAX];                            //表示当前在线人数组
FriendList friends[FRIEND_MAX];                     // 全局好友列表,存储所有用户之间的关系

// 函数声明
void* handle_client(void* arg);                     // 线程
int socket_init (void);                             // 初始化套接字
int socket_accept (int listen_socket);              // 等待客户端连接
int login(int client_socket, Msg msg,MYSQL mysql,MYSQL* conn);    // 登录
void regisc(int client_socket, Msg msg,MYSQL mysql,MYSQL* conn);  // 注册
void user_server(int client_socket, Msg* msg);      // 用户命令
void server_chatall(int client_socket, Msg* msg);   // 群聊
void chatone_apply(int client_socket, Msg* msg);    // 发送私聊申请
void friend_list(int client_socket, Msg* msg);      // 好友列表
void send_chatone(int client_socket, Msg* msg);     // 发送私聊消息
void send_file(int client_socket, Msg* msg);        // 上传文件
void chatone_reply(int client_socket, Msg* msg);    // 私聊反馈
void group_records(int client_socket,Msg* msg);     // 获取群聊记录
void private_records(int client_socket,Msg* msg);    // 获取私聊记录
void save_chat(Msg* msg);                           // 保存聊天记录
void user_logout(int client_socket, Msg* msg);      // 退出登录

int main(){
    // 初始化套接字
	int listen_socket = socket_init();
    error_message(listen_socket,"初始化套接字失败\n");

    // 循环等待客户端连接
    while (1){
        // 阻塞，等待客户端连接，成功则返回新的文件描述符，用于和客户端通信
        // 在64位系统中，void*的大小是8字节，而int的大小是4字节,因此这里使用动态内存分配
        int *pclient = malloc(sizeof(int));
        int client_socket = socket_accept(listen_socket);
        error_message(client_socket,"客户端连接失败\n");

        *pclient = client_socket;
        // 创建线程
        pthread_t tid;
        pthread_create(&tid,NULL,handle_client,pclient);
        pthread_detach(tid);
    }

    // 释放内存
    close(listen_socket);
    return 0;

}


// 线程
void *handle_client(void* arg) {
    MYSQL mysql;
    MYSQL* conn;
    Msg msg;
    int to_user_index = 0;

    int client_socket = *(int*)arg;
    free(arg);

    while (1){   // 当客户端退出时,结束循环
        // 接收客户端数据
        int recv_len = recv(client_socket,&msg,sizeof(Msg),0);
        error_message(recv_len,"数据接收失败\n");

		// 代表客户端退出
		if (recv_len == 0 || msg.header.cmd == 0){
			printf ("客户端退出\n");
			break;
		}

        switch (msg.header.cmd){
        case 1: // 登陆功能
            to_user_index = login(client_socket, msg, mysql, conn);
            break;
        case 2: // 注册功能
            regisc(client_socket, msg, mysql, conn);
            break;
        default:
            printf("没有该命令\n");
            break;
        }
    }

    // 如果登录成功则进入用户主页相关命令选择
    if (to_user_index){
        user_server(client_socket, &msg);
    }
    
    // 关闭客户端连接
    close(client_socket);
    return NULL;
}


// 初始化套接字
int socket_init (void){
    struct sockaddr_in serv_addr;
    int optval = 1;

    int listen_socket = socket(AF_INET,SOCK_STREAM,0);
    error_message(listen_socket,"套接字创建失败\n");
    // 绑定前先构造出服务器地址
    bzero(&serv_addr,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    // 网络字节序
    serv_addr.sin_port = htons(SERV_PORT);
    // 指定主机所有ip
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    int set_socket = setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
    error_message(set_socket,"设置套接字失败\n");
    int ret = bind(listen_socket,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
    error_message(ret,"绑定套接字失败,上一个进程未释放该端口\n");

    // 监听并设置服务器能接收并发链接的能力
    int lis = listen(listen_socket,10);
    error_message(lis,"监听套接字失败\n");
    printf("等待客户端连接.....\n");
	return listen_socket;
}


// 等待客户端连接
int socket_accept (int listen_socket){
    struct sockaddr_in client_addr;
	int len = sizeof(client_addr);

    // 等待客户端连接
    int client_socket = accept(listen_socket, (struct sockaddr*)&client_addr, &len);
    error_message(client_socket,"链接客户端失败\n");
    printf("成功链接了一个客户端 : %s\n", inet_ntoa(client_addr.sin_addr));
    return client_socket;
}


// 用户界面
void user_server(int client_socket, Msg* msg){
	int chat_index = 1;      // 表示循环退出条件
    int read_len;
	while(chat_index){
		// 从客户端读取数据
		read_len = recv(client_socket, msg, sizeof(Msg),0);
        error_message(read_len,"数据接收失败\n");

		// 代表客户端退出
		if (read_len == 0 || msg->header.cmd == 0){
            chat_index = 0;
			break;
		}
		switch (msg->header.cmd){
			case 3 :    // 群聊
				server_chatall(client_socket, msg);
				break;
			case 4 :    // 发送私聊申请
				chatone_apply(client_socket, msg);
				break;
			case 6 :    // 好友列表
				friend_list(client_socket, msg);
				break;
            case 7 :    // 发送私聊消息
				send_chatone(client_socket, msg);
				break;
            case 8 :    // 发送文件
				send_file(client_socket, msg);
				break;
            case 9 :    // 私聊反馈
                chatone_reply(client_socket, msg);
                break;
            case 10 :    // 获取群聊记录
                group_records(client_socket, msg);
                break;
            case 11 :    // 获取私聊记录
                private_records(client_socket, msg);
                break;
		}
	}
	
	//用户下线
    user_logout(client_socket, msg);
	
}


// 登录
int login(int client_socket, Msg msg,MYSQL mysql,MYSQL* conn){
    char select_sql[1024];
    int send_len;
    // 先判断用户是否重复登录,如果重复登录则提示客户端不能重复登录
    for (int i = 0; i < USER_MAX; i++)
    {
        if (strcmp(users[i].name, msg.login.username) == 0)
        {
            msg.login.isLogin = 3;
            send(client_socket,&msg,sizeof(msg),0);
            error_message(send_len,"数据发送失败\n");
            return 0;
        }
        
    }
    

    // 与数据库建立链接
    conn = initMysql(&mysql);
    // 拼接sql
    memset(select_sql, 0, sizeof(select_sql));
    sprintf(select_sql, "SELECT * FROM user WHERE username='%s' AND password='%s'", msg.login.username, msg.login.password);
    // 查询数据库,拿到查询结果
    int res = loginSelect(&mysql, select_sql);
    // 关闭数据库连接
    mysql_close(conn);

    // 设置发送给客户端的命令
    msg.header.cmd = 1;
    if (res == 1){ // 如果查询结果数量等于1条,说明存在该用户,允许登录
        printf("登录成功!\n");
        // 登录成功后添加该用户至在线人数数组中,将在线人数加1
        for (int i=0; i< USER_MAX; i++){
            if(users[i].socket == 0){
                strcpy(users[i].name, msg.login.username);
                users[i].socket = client_socket;
                printf("客户端在线人数加一\n");
                break;
            }
        }

        // 用户界面
        msg.login.isLogin = 1; // 设置isLogin状态码为1,告诉客户端登录成功
        send_len = send(client_socket,&msg,sizeof(Msg),0);
        error_message(send_len,"数据发送失败\n");
        user_server(client_socket, &msg);
        return 1;   // 登录成功返回1,进入用户主页进行群聊等相关功能
    } else{
        // 登录失败,设置isLogin状态码为2,告诉客户端登录失败
        printf("登录失败!\n");
        msg.login.isLogin = 2;
        send(client_socket,&msg,sizeof(msg),0);
        error_message(send_len,"数据发送失败\n");
        return 0;   // 登录失败返回0,不进行功能跳转
    }
}


// 注册
void regisc(int client_socket, Msg msg,MYSQL mysql,MYSQL* conn){
    char insert_sql[1024];

    // 与数据库建立连接
    conn = initMysql(&mysql);

    // 在注册时进行数据库操作，插入注册信息
    sprintf(insert_sql, "INSERT INTO user (username, password) VALUES ('%s',  '%s')", msg.regisc.username, msg.regisc.password);
    int res = c_db_insert(&mysql, insert_sql);
    if (res == 1) {
        printf("注册成功!\n");
        msg.regisc.status = 1;  // 设置status为1,告诉客户端注册成功
    } else {
        printf("注册失败!\n");
        msg.regisc.status = 0;  // 设置status为0,告诉客户端注册失败
    }
    mysql_close(conn);  // 释放数据库连接
    int send_len = send(client_socket, &msg, sizeof(msg), 0);
    error_message(send_len,"数据发送失败\n");
}



//群聊
void server_chatall(int client_socket, Msg* msg){
    // 设置响应命令
    msg->header.cmd = 12;
    // 循环在线用户数组,将消息发送给每一个用
	for(int i=0; i<USER_MAX; i++){
		if (users[i].socket != 0){
			int send_len = send(users[i].socket, msg , sizeof(Msg),0);
            error_message(send_len,"数据发送失败\n");
		}
	}
    // 保存聊天记录到本地
    save_chat(msg);
}


//发送私聊申请
void chatone_apply(int client_socket, Msg* msg){
    int flag = 0;
    int toUserClient = 0;
    int send_len;

    // 设置响应命令
    msg->header.cmd = 13;
    // 从在线数组中查找对方是否在线,在线则将flag置为1,同时将toUserClient置为目标用户的client标识符.
	for(int i=0; i<USER_MAX; i++){
		if(users[i].socket != 0 
           && strncmp(users[i].name, msg->chat.toUser, strlen(msg->chat.toUser)) == 0){
            flag = 1;
            toUserClient = users[i].socket;
			break;
		}
	}

    // 如果对方在线则发送申请，否则提示客户端对方不在线
    if (flag){
        // 对方在线则将申请消息发送给对方
        msg->chat.is_on_line = 1;   
        send_len = send(toUserClient, msg , sizeof(Msg),0);
        error_message(send_len,"数据发送失败\n");
    } else{
        // 否则告诉申请人对方不在线
        msg->chat.is_on_line = 0;   
        send_len = send(client_socket, msg , sizeof(Msg),0);
        error_message(send_len,"数据发送失败\n");
    }
    
}


//查看好友列表
void friend_list(int client_socket, Msg* msg){
    FriendList variable_friends[FRIEND_MAX];    // 临时存储好友列表
    int i = 0;
    int j = 0;
    
    bzero(variable_friends,sizeof(variable_friends));

    // 遍历全局好友列表,从全局好友关系数组中查找与当前用户有好友关系的用户,并存储至临时好友列表中返回给客户端
    for (i = 0,j = 0; i < FRIEND_MAX; i++) {
        if (friends[i].flag != 0 
            && (strcmp(friends[i].fromUser, msg->chat.fromUser) == 0 
            || strcmp(friends[i].toUser, msg->chat.fromUser) == 0)){

            variable_friends[j++] = friends[i];
            }
    }
    int send_len = send(client_socket, &variable_friends , sizeof(variable_friends),0);
    error_message(send_len,"数据发送失败\n");
}



//发送私聊消息
void send_chatone(int client_socket, Msg* msg){
	int isOnline = 0;
    int toUserClient = 0;
    int send_len;
    // 设置接收私聊消息命令
    msg->header.cmd = 14;
    // 每次发消息之前都遍历一次在线用户数组,查看接收方是否在线
    for(int i=0; i<USER_MAX; i++){
		if(strcmp(users[i].name, msg->chat.toUser) == 0 && users[i].socket != 0){
            isOnline = 1;
            toUserClient = users[i].socket;
            break;
		}
	}

    // 如果在线则正常发送消息,否则提示发送方,对方不在线
    if(isOnline){
        msg->chat.is_on_line = 1;
        send_len = send(toUserClient, msg , sizeof(Msg),0);
        error_message(send_len,"数据发送失败\n");
        // 保存聊天记录到本地
        save_chat(msg);
    } else{
        msg->chat.is_on_line = 0;
        send_len = send(client_socket, msg , sizeof(Msg),0);
        error_message(send_len,"数据发送失败\n");
    }
}


// 发送文件
void send_file(int client_socket, Msg* msg){
    int val_read,send_len;
    int to_user_client = 0;
    

    for (int i = 0; i < USER_MAX; i++){
        if (users[i].socket != 0 && strcmp(users[i].name, msg->chat.toUser) == 0 ){
            to_user_client = users[i].socket;
            break;
        }
    }
    
    if (to_user_client != 0){
        msg->header.cmd = 14;
        msg->chat.is_on_line = 1;
        send_len = send(to_user_client, msg , sizeof(msg),0);
        error_message(send_len,"数据发送失败\n");
    }else {
        msg->header.cmd = 14;
        msg->chat.is_on_line = 0;
        send_len = send(client_socket, msg , sizeof(Msg),0);
        error_message(send_len,"数据发送失败\n");
    }
}



// 私聊反馈
void chatone_reply(int client_socket,Msg* msg){
    int send_len;
    // 获取两人的client标识符
    int fromUserClient = -1;
    int toUserClient = -1;
    int flag = 0;
    msg->header.cmd = 16;

    // 先依据双方用户名从在线用户数组中找到双方的client标识符
    for (int i = 0; i < USER_MAX; i++) {
        if (strcmp(users[i].name, msg->chat.fromUser) == 0) {
            fromUserClient = users[i].socket;
        }
        if (strcmp(users[i].name, msg->chat.toUser) == 0) {
            toUserClient = users[i].socket;
        }
        // 如果两个client标识符都找到了，则跳出循环
        if (fromUserClient != -1 && toUserClient != -1) {
            break;
        }
    }
    

    // 如果对方同意私聊,则遍历好友列表，查看是否有重复申请添加
    if(msg->chat.is_consent_chat == 1){
        for (int i = 0; i < FRIEND_MAX; i++) {
            if (strcmp(friends[i].fromUser, msg->chat.fromUser) == 0 
                && strcmp(friends[i].toUser, msg->chat.toUser) == 0){
                // 如果好友列表已经存在该用户则将flag置为1,并退出循环.
                flag = 1;
                break;
            }
        }

        // 如果好友列表不存在该用户,则在好友数组最后的空闲位置添加好友至列表中
        if(!flag){
            //好友列表人数加1
            // 发送方添加对方至好友列表
            for (int i=0; i< USER_MAX; i++){
                if(friends[i].flag == 0){   
                    strcpy(friends[i].fromUser, msg->chat.fromUser);
                    friends[i].fromClient = fromUserClient;
                    strcpy(friends[i].toUser, msg->chat.toUser);
                    friends[i].toClient = toUserClient;
                    friends[i].flag = 1;
                    break;
                }
            }

            // 接收方也需要添加对方至好友列表
            for (int i=0; i< USER_MAX; i++){
                if(friends[i].flag == 0){   
                    strcpy(friends[i].toUser, msg->chat.fromUser);
                    friends[i].toClient = fromUserClient;
                    strcpy(friends[i].fromUser, msg->chat.toUser);
                    friends[i].fromClient = toUserClient;
                    friends[i].flag = 1;
                    break;
                }
            }
        }


        // 对方同意私聊则将is_consent_chat置为1,告诉客户端对方同意好友申请
        msg->chat.is_consent_chat = 1;
        
        send_len = send(fromUserClient, msg , sizeof(Msg),0);
        error_message(send_len,"数据发送失败\n");
    } else{  
        // 对方不同意私聊则将is_consent_chat置为0,告诉客户端对方拒绝好友申请
        msg->chat.is_consent_chat = 0;
        send_len = send(fromUserClient, msg , sizeof(Msg),0);
        error_message(send_len,"数据发送失败\n");
    }
}


// 获取群聊记录
void group_records(int client_socket,Msg* msg){
    // 接收文件名
    char file_path[1024];
    int source_fd;
    char buffer[FILE_SIZE];
    
    memset(file_path, 0, sizeof(file_path));
    // 获取当前路径
    getcwd(file_path, sizeof(file_path));
    // 拼接当前上传文件的路径
    strcat(file_path, "/groupChat.txt");

    // 打开文件
    source_fd = open(file_path, O_RDONLY);
    if (source_fd == -1) {
        perror("文件打开失败");
        return ;
    }

    // 从源文件读取内容
    read(source_fd, buffer, FILE_SIZE);
    strcpy(msg->chat.message, buffer);
    printf("%s",msg->chat.message);
    int send_len = send(client_socket,&msg,sizeof(msg),0);
    error_message(send_len,"数据发送失败\n");
    close(source_fd);
}



// 获取私聊记录
void private_records(int client_socket,Msg* msg){
    // 接收文件名
    char file_path[1024];
    int source_fd;
    char buffer[FILE_SIZE];
    
    memset(file_path, 0, sizeof(file_path));
    // 获取当前路径
    getcwd(file_path, sizeof(file_path));
    // 拼接当前上传文件的路径
    strcat(file_path, "/oneChat.txt");

    // 打开文件
    source_fd = open(file_path, O_RDONLY);
    if (source_fd == -1) {
        perror("文件打开失败");
        return ;
    }

    // 从源文件读取内容并写入目标文件
    read(source_fd, buffer, FILE_SIZE);
    strcpy(msg->chat.message, buffer);
    msg->header.cmd = 18;
    printf("%s",msg->chat.message);
    int send_len = send(client_socket,&msg,sizeof(msg),0);
    error_message(send_len,"数据发送失败\n");
    close(source_fd);
}


// 保存消息日志文件
void save_chat(Msg* msg) {
    char message[4096]; // 设置一个2k大小的数组接收小型文件
    if (msg->chat.group_or_private==1){ //cmd=3说明是群聊,则将群聊信息统一记录至groupChat.txt文件中

        // 记录每一条消息的发送人,消息内容,时间等信息
        sprintf(message, "%s: %s  %s\n", msg->chat.fromUser, msg->chat.message, msg->chat.time);
        FILE *file = fopen("groupChat.txt", "a"); // 打开文件，如果不存在则创建
        if (file != NULL) {
            fprintf(file, "%s\n", message); // 将消息写入文件
            fclose(file); // 关闭文件
        } else {
            printf("文件写入失败\n");
        }
    } else if (msg->chat.group_or_private==2){  // 如果不是群聊则是私聊,将私聊信息记录起来
        sprintf(message, "%s: %s  %s\n", msg->chat.fromUser, msg->chat.message, msg->chat.time);
        FILE *file = fopen("oneChat.txt", "a"); // 打开文件，如果不存在则创建
        if (file != NULL) {
            fprintf(file, "%s\n", message); // 将消息写入文件
            fclose(file); // 关闭文件
        } else {
            printf("文件写入失败\n");
        }
    }
}


// 退出登录
void user_logout(int client_socket, Msg* msg) {
    int send_len;
    msg->header.cmd=11;
    for(int i = 0; i < USER_MAX; i++){
        if(users[i].socket == client_socket){
            users[i].socket = 0;
            printf("客户端在线人数减一\n");
            char logoutusername[50];
            memset(logoutusername, 0, sizeof(logoutusername));
            strcpy(logoutusername, users[i].name);
            strcat(logoutusername," 已下线");
            strcpy(msg->chat.message, logoutusername);
            for(int j=0; j<USER_MAX; j++){
                if (users[j].socket != 0){   
                    send_len = send(users[j].socket, msg , sizeof(Msg),0);
                    error_message(send_len,"数据发送失败\n");
                }
            }
            break;
        }
    }
}
